/*-----------------------------------------------------------------------
 *
 * PostgreSQL locale utilities
 *
 * Portions Copyright (c) 2002-2022, PostgreSQL Global Development Group
 *
 * src/backend/utils/adt/pg_locale.c
 *
 *-----------------------------------------------------------------------
 */

/*----------
 * 本地化的处理方式如下：LC_COLLATE 和 LC_CTYPE
 * 在创建数据库时固定，存储在 pg_database 中，不能
 * 被更改。因此，strcoll()、strxfrm()、isupper()、toupper() 等
 * 的效果始终在同一固定的区域中。
 *
 * LC_MESSAGES 在运行时可以设置，并将立即生效。
 *
 * 其他类别，LC_MONETARY、LC_NUMERIC 和 LC_TIME 也可以
 * 在运行时设置。然而，我们并未实际永久设置这些区域
 * 类别。这会导致奇怪的效果，比如在某些区域不再接受
 * 标准浮点字面量。相反，我们仅在需要时简短地设置
 * 这些区域类别，缓存从 localeconv() 或 strftime()
 * 获取的必要信息，然后将区域类别恢复为 "C"。
 * 缓存的信息仅由格式化函数（to_char 等）和货币类型使用。
 * 对于用户来说，这一切应该都是透明的。
 *
 * !!! 请注意 !!!
 *
 * 我们曾多次被这个错误所困扰，因此请在今后记住：在某些平台上，
 * 区域函数返回指向静态数据的指针，这些数据会被后续的任何区域
 * 函数覆盖。因此，例如，明显的序列
 *			save = setlocale(category, NULL);
 *			if (!setlocale(category, value))
 *				fail = true;
 *			setlocale(category, save);
 * 并不可靠：在某些平台上第二个 setlocale() 调用会更改
 * save 正指向的内存。要安全地进行此类操作，你 *必须* 首次
 * pstrdup setlocale 返回的内容。
 *
 * POSIX 区域标准可以在这里找到：
 *
 *	http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap07.html
 *----------
 */


#include "postgres.h"

#include <time.h>

#include "access/htup_details.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_control.h"
#include "mb/pg_wchar.h"
#include "utils/builtins.h"
#include "utils/formatting.h"
#include "utils/hsearch.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/pg_locale.h"
#include "utils/syscache.h"

#ifdef USE_ICU
#include <unicode/ucnv.h>
#endif

#ifdef __GLIBC__
#include <gnu/libc-version.h>
#endif

#ifdef WIN32
#include <shlwapi.h>
#endif

#define		MAX_L10N_DATA		80


/* GUC 设置 */
char	   *locale_messages;
char	   *locale_monetary;
char	   *locale_numeric;
char	   *locale_time;

/*
 * lc_time 本地化缓存。
 *
 * 我们只使用这些数组的前 7 或 12 个条目。最后一个数组
 * 元素保留为 NULL，方便外部代码顺序扫描这些数组。
 */
char	   *localized_abbrev_days[7 + 1];
char	   *localized_full_days[7 + 1];
char	   *localized_abbrev_months[12 + 1];
char	   *localized_full_months[12 + 1];

/* 数据库的 LC_CTYPE 是 C 区域吗？ */
bool		database_ctype_is_c = false;

/* 表示区域信息缓存是否有效 */
static bool CurrentLocaleConvValid = false;
static bool CurrentLCTimeValid = false;

/* 关于排序相关知识的缓存 */

typedef struct
{
	Oid			collid;			/* 哈希键：pg_collation OID */
	bool		collate_is_c;	/* 排序的 LC_COLLATE 是 C 吗？ */
	bool		ctype_is_c;		/* 排序的 LC_CTYPE 是 C 吗？ */
	bool		flags_valid;	/* 如果上述标志有效则为真 */
	pg_locale_t locale;			/* locale_t 结构，若无效则为 0 */
} collation_cache_entry;

static HTAB *collation_cache = NULL;


#if defined(WIN32) && defined(LC_MESSAGES)
static char *IsoLocaleName(const char *);	/* MSVC 特定 */
#endif

#ifdef USE_ICU
static void fc_icu_set_collation_attributes(UCollator *fc_collator, const char *fc_loc);
#endif

/*
 * pg_perm_setlocale
 *
 * 这封装了 libc 函数 setlocale()，并增加了两个功能。首先，当
 * 更改 LC_CTYPE 时，更新当前消息域的 gettext 编码。GNU gettext
 * 在大多数平台上自动跟踪 LC_CTYPE，但在 Windows 上却不是。第二，如果
 * 操作成功，相应的 LC_XXX 环境变量将被设置以匹配。通过设置环境
 * 变量，我们确保通过此例程做出的任何后续 setlocale(..., "") 调用
 * 将保留所做的设置。当然，必须取消设置 LC_ALL 才能完全确保，
 * 但所有单个 LC_XXX 变量正确设置后，这是必须在其他地方完成的。
 * （感谢 Perl 让这个解决方案变得必要。）
 */
char * pg_perm_setlocale(int fc_category, const char *fc_locale)
{
	char	   *fc_result;
	const char *fc_envvar;

#ifndef WIN32
	fc_result = setlocale(fc_category, fc_locale);
#else

	/*
	 * 在 Windows 上，setlocale(LC_MESSAGES) 无法工作，因此只假设
	 * 给定值是好的，并将其设置在环境变量中。我们必须忽略设置为 ""
	 * 的尝试，这意味着“继续使用旧的环境值”。
	 */
#ifdef LC_MESSAGES
	if (category == LC_MESSAGES)
	{
		fc_result = (char *) fc_locale;
		if (fc_locale == NULL || fc_locale[0] == '\0')
			return fc_result;
	}
	else
#endif
		fc_result = setlocale(category, fc_locale);
#endif							/* WIN32 */

	if (fc_result == NULL)
		return fc_result;			/* 失败时立即退出 */

	/*
	 * 在翻译的消息中使用正确的编码。启用 ENABLE_NLS 下，
	 * 让 pg_bind_textdomain_codeset() 来确定。在不启用 ENABLE_NLS 的情况下，
	 * 消息格式字符串是 ASCII，但数据库编码字符串可能通过 %s 进入消息。
	 * 这使总体消息编码等于数据库编码。
	 */
	if (fc_category == LC_CTYPE)
	{
		static char fc_save_lc_ctype[LOCALE_NAME_BUFLEN];

		/* 在被调用者再次调用之前复制 setlocale() 返回值 */
		strlcpy(fc_save_lc_ctype, fc_result, sizeof(fc_save_lc_ctype));
		fc_result = fc_save_lc_ctype;

#ifdef ENABLE_NLS
		SetMessageEncoding(pg_bind_textdomain_codeset(textdomain(NULL)));
#else
		SetMessageEncoding(GetDatabaseEncoding());
#endif
	}

	switch (fc_category)
	{
		case LC_COLLATE:
			fc_envvar = "LC_COLLATE";
			break;
		case LC_CTYPE:
			fc_envvar = "LC_CTYPE";
			break;
#ifdef LC_MESSAGES
		case LC_MESSAGES:
			fc_envvar = "LC_MESSAGES";
#ifdef WIN32
			fc_result = IsoLocaleName(fc_locale);
			if (fc_result == NULL)
				fc_result = (char *) fc_locale;
			elog(DEBUG3, "IsoLocaleName() executed; locale: \"%s\"", fc_result);
#endif							/* WIN32 */
			break;
#endif							/* LC_MESSAGES */
		case LC_MONETARY:
			fc_envvar = "LC_MONETARY";
			break;
		case LC_NUMERIC:
			fc_envvar = "LC_NUMERIC";
			break;
		case LC_TIME:
			fc_envvar = "LC_TIME";
			break;
		default:
			elog(FATAL, "unrecognized LC category: %d", fc_category);
			return NULL;		/* 保持编译器安静 */
	}

	if (setenv(fc_envvar, fc_result, 1) != 0)
		return NULL;

	return fc_result;
}


/*
 * 该区域名称在区域类别中是否有效？
 *
 * 如果成功，并且 canonname 不为 NULL，则该区域的
 * 规范名称的 palloc 副本将存储在那里。这对于确定
 * 区域名称 "" 的含义（即，服务器环境值）尤其有用。（实际上，
 * 在大多数实现中，这似乎是它唯一有用的地方；
 * 我们希望 setlocale 返回一个规范拼写的区域名称，但通常并不是。）
 */
bool check_locale(int fc_category, const char *fc_locale, char **fc_canonname)
{
	char	   *fc_save;
	char	   *fc_res;

	if (fc_canonname)
		*fc_canonname = NULL;		/* 在失败的情况下 */

	fc_save = setlocale(fc_category, NULL);
	if (!fc_save)
		return false;			/* 不会发生，我们希望如此 */

	/* save 可能指向一个可修改的临时变量，见上文。 */
	fc_save = pstrdup(fc_save);

	/* 使用setlocale设置区域，看看它是否接受。 */
	fc_res = setlocale(fc_category, fc_locale);

	/* 如果请求，则保存规范名称。 */
	if (fc_res && fc_canonname)
		*fc_canonname = pstrdup(fc_res);

	/* 恢复旧值。 */
	if (!setlocale(fc_category, fc_save))
		elog(WARNING, "failed to restore old locale \"%s\"", fc_save);
	pfree(fc_save);

	return (fc_res != NULL);
}


/* 
 * GUC 检查/赋值钩子
 *
 * 对于大多数区域设置类别，赋值钩子实际上并不会永久设置区域设置，
 * 只是重置标志，以便下一次使用时会缓存
 * 适当的值。 （请参见此文件顶部的解释。）
 *
 * 注意：我们接受 value = "" 作为选择主进程的环境
 * 值，无论它是什么（只要环境设置是合法的）。
 * 这将通过对 pg_perm_setlocale 的早期调用被锁定。
 */
bool check_locale_monetary(char **fc_newval, void **fc_extra, GucSource fc_source)
{
	return check_locale(LC_MONETARY, *fc_newval, NULL);
}

void assign_locale_monetary(const char *fc_newval, void *fc_extra)
{
	CurrentLocaleConvValid = false;
}

bool check_locale_numeric(char **fc_newval, void **fc_extra, GucSource fc_source)
{
	return check_locale(LC_NUMERIC, *fc_newval, NULL);
}

void assign_locale_numeric(const char *fc_newval, void *fc_extra)
{
	CurrentLocaleConvValid = false;
}

bool check_locale_time(char **fc_newval, void **fc_extra, GucSource fc_source)
{
	return check_locale(LC_TIME, *fc_newval, NULL);
}

void assign_locale_time(const char *fc_newval, void *fc_extra)
{
	CurrentLCTimeValid = false;
}

/* 
 * 我们允许 LC_MESSAGES 实际上被全局设置。
 *
 * 注意：我们通常不允许 value = ""，因为它没有一致的
 * 语义（它实际上只是使用之前的值）。 但是，这
 * 是传递给 PGC_S_DEFAULT 的值，因此在这种情况下
 * 不要抱怨，即使尝试设置因无效的环境值而失败。
 * 这里的想法是在启动期间尽可能接受环境设置，
 * 直到我们可以从 postgresql.conf 读取正确的值。
 */
bool check_locale_messages(char **fc_newval, void **fc_extra, GucSource fc_source)
{
	if (**fc_newval == '\0')
	{
		if (fc_source == PGC_S_DEFAULT)
			return true;
		else
			return false;
	}

	/* 
	 * LC_MESSAGES 类别并非无处存在，但仍然接受
	 *
	 * 在 Windows 上，我们甚至无法检查值，因此盲目接受
	 */
#if defined(LC_MESSAGES) && !defined(WIN32)
	return check_locale(LC_MESSAGES, *fc_newval, NULL);
#else
	return true;
#endif
}

void assign_locale_messages(const char *fc_newval, void *fc_extra)
{
	/* 
	 * LC_MESSAGES 类别并非无处存在，但仍然接受。
	 * 我们忽略失败，如上面的注释所述。
	 */
#ifdef LC_MESSAGES
	(void) pg_perm_setlocale(LC_MESSAGES, fc_newval);
#endif
}


/* 
 * 释放一个 struct lconv 的 malloc 内容。 （但是不释放结构
 * 本身。）重要的是，这不会抛出 elog(ERROR)。
 */
static void fc_free_struct_lconv(struct lconv *fc_s)
{
	if (fc_s->decimal_point)
		free(fc_s->decimal_point);
	if (fc_s->thousands_sep)
		free(fc_s->thousands_sep);
	if (fc_s->grouping)
		free(fc_s->grouping);
	if (fc_s->int_curr_symbol)
		free(fc_s->int_curr_symbol);
	if (fc_s->currency_symbol)
		free(fc_s->currency_symbol);
	if (fc_s->mon_decimal_point)
		free(fc_s->mon_decimal_point);
	if (fc_s->mon_thousands_sep)
		free(fc_s->mon_thousands_sep);
	if (fc_s->mon_grouping)
		free(fc_s->mon_grouping);
	if (fc_s->positive_sign)
		free(fc_s->positive_sign);
	if (fc_s->negative_sign)
		free(fc_s->negative_sign);
}

/* 
 * 检查一个 struct lconv 的所有字段（或者至少是我们关心的字段）
 * 是否为非 NULL。 字段列表必须与 free_struct_lconv() 匹配。
 */
static bool fc_struct_lconv_is_valid(struct lconv *fc_s)
{
	if (fc_s->decimal_point == NULL)
		return false;
	if (fc_s->thousands_sep == NULL)
		return false;
	if (fc_s->grouping == NULL)
		return false;
	if (fc_s->int_curr_symbol == NULL)
		return false;
	if (fc_s->currency_symbol == NULL)
		return false;
	if (fc_s->mon_decimal_point == NULL)
		return false;
	if (fc_s->mon_thousands_sep == NULL)
		return false;
	if (fc_s->mon_grouping == NULL)
		return false;
	if (fc_s->positive_sign == NULL)
		return false;
	if (fc_s->negative_sign == NULL)
		return false;
	return true;
}


/* 
 * 将指定编码下的 *str 中的 strdup 字符串转换为
 * 数据库编码。
 */
static void fc_db_encoding_convert(int fc_encoding, char **fc_str)
{
	char	   *fc_pstr;
	char	   *fc_mstr;

	/* 将字符串转换为数据库编码 */
	fc_pstr = pg_any_to_server(*fc_str, strlen(*fc_str), fc_encoding);
	if (fc_pstr == *fc_str)
		return;					/* 没有发生转换 */

	/* 需要它 malloc 而不是 palloc */
	fc_mstr = strdup(fc_pstr);
	if (fc_mstr == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory")));

	/* 替换旧字符串 */
	free(*fc_str);
	*fc_str = fc_mstr;

	pfree(fc_pstr);
}


/* 
 * 返回 POSIX lconv 结构（包含数字/货币格式
 * 信息），以及所有类别的区域信息。
 */
struct lconv *
PGLC_localeconv(void)
{
	static struct lconv fc_CurrentLocaleConv;
	static bool fc_CurrentLocaleConvAllocated = false;
	struct lconv *fc_extlconv;
	struct lconv fc_worklconv;
	char	   *fc_save_lc_monetary;
	char	   *fc_save_lc_numeric;
#ifdef WIN32
	char	   *save_lc_ctype;
#endif

	/* 我们已经做过了吗？ */
	if (CurrentLocaleConvValid)
		return &fc_CurrentLocaleConv;

	/* 释放任何已分配的存储 */
	if (fc_CurrentLocaleConvAllocated)
	{
		fc_free_struct_lconv(&fc_CurrentLocaleConv);
		fc_CurrentLocaleConvAllocated = false;
	}

	/* 
	 * 这很棘手，因为我们真的不想在区域设置
	 * 被设置为与我们通常的设置不同的情况下冒风险抛出错误。
	 * 因此，过程是：收集通常的设置，将区域设置更改为特殊
	 * 设置，使用 strdup() 将相关数据复制到 worklconv，恢复
	 * 正常设置，将数据转换为所需编码，最后将
	 * 收集到的数据存储到 CurrentLocaleConv 中。 这样，如果我们
	 * 在编码转换期间抛出错误或在过程中内存不足，都是安全的。
	 * struct lconv 成员指向的所有数据都是
	 * 使用 strdup 分配的，以避免过早的 elog(ERROR) 并允许
	 * 使用一个单一的清理例程。
	 */
	memset(&fc_worklconv, 0, sizeof(fc_worklconv));

	/* 保存货币和数字区域设置的当前值 */
	fc_save_lc_monetary = setlocale(LC_MONETARY, NULL);
	if (!fc_save_lc_monetary)
		elog(ERROR, "setlocale(NULL) failed");
	fc_save_lc_monetary = pstrdup(fc_save_lc_monetary);

	fc_save_lc_numeric = setlocale(LC_NUMERIC, NULL);
	if (!fc_save_lc_numeric)
		elog(ERROR, "setlocale(NULL) failed");
	fc_save_lc_numeric = pstrdup(fc_save_lc_numeric);

#ifdef WIN32

	/* 
	 * POSIX 标准明确表示，如果 LC_MONETARY 或 LC_NUMERIC
	 * 隐含与 LC_CTYPE 不同的编码（字符集），则结果是未定义的。
	 * 在实践中，所有类 Unix 平台似乎都认为 localeconv() 应该返回
	 * 使用 LC_MONETARY 或 LC_NUMERIC 区域名隐含的字符集编码的字符串。
	 * 因此，一旦我们成功收集了 localeconv() 的结果，我们将
	 * 从该字符集转换为所需的服务器编码。
	 *
	 * 当然，Windows 坚决以其自己的方式做事；在该
	 * 平台上，LC_CTYPE 必须与 LC_MONETARY/LC_NUMERIC 匹配才能获得合理的
	 * 结果。 因此，我们必须暂时设置该类别。
	 */

	/* 保存 ctype 区域的当前值 */
	save_lc_ctype = setlocale(LC_CTYPE, NULL);
	if (!save_lc_ctype)
		elog(ERROR, "setlocale(NULL) failed");
	save_lc_ctype = pstrdup(save_lc_ctype);

	/* 这里开始了关键部分，我们必须避免投掷错误 */

	/* 使用数字设置 ctype */
	setlocale(LC_CTYPE, locale_numeric);
#endif

	/* 获取数字的格式信息 */
	setlocale(LC_NUMERIC, locale_numeric);
	fc_extlconv = localeconv();

	/* 现在必须复制数据，以防 setlocale() 会覆盖它 */
	fc_worklconv.decimal_point = strdup(fc_extlconv->decimal_point);
	fc_worklconv.thousands_sep = strdup(fc_extlconv->thousands_sep);
	fc_worklconv.grouping = strdup(fc_extlconv->grouping);

#ifdef WIN32
	/* 使用货币设置 ctype */
	setlocale(LC_CTYPE, locale_monetary);
#endif

	/* 获取货币的格式信息 */
	setlocale(LC_MONETARY, locale_monetary);
	fc_extlconv = localeconv();

	/* 现在必须复制数据，以防 setlocale() 会覆盖它 */
	fc_worklconv.int_curr_symbol = strdup(fc_extlconv->int_curr_symbol);
	fc_worklconv.currency_symbol = strdup(fc_extlconv->currency_symbol);
	fc_worklconv.mon_decimal_point = strdup(fc_extlconv->mon_decimal_point);
	fc_worklconv.mon_thousands_sep = strdup(fc_extlconv->mon_thousands_sep);
	fc_worklconv.mon_grouping = strdup(fc_extlconv->mon_grouping);
	fc_worklconv.positive_sign = strdup(fc_extlconv->positive_sign);
	fc_worklconv.negative_sign = strdup(fc_extlconv->negative_sign);
	/* 同样复制标量字段 */
	fc_worklconv.int_frac_digits = fc_extlconv->int_frac_digits;
	fc_worklconv.frac_digits = fc_extlconv->frac_digits;
	fc_worklconv.p_cs_precedes = fc_extlconv->p_cs_precedes;
	fc_worklconv.p_sep_by_space = fc_extlconv->p_sep_by_space;
	fc_worklconv.n_cs_precedes = fc_extlconv->n_cs_precedes;
	fc_worklconv.n_sep_by_space = fc_extlconv->n_sep_by_space;
	fc_worklconv.p_sign_posn = fc_extlconv->p_sign_posn;
	fc_worklconv.n_sign_posn = fc_extlconv->n_sign_posn;

	/* 
	 * 恢复现行的区域设置；未能做到这一点是致命的。
	 * 我们可能可以在 LC_MONETARY 或 LC_NUMERIC 被设置为非默认值的情况下勉强继续，
	 * 但处理错误的 LC_CTYPE 值肯定是坏消息；
	 * 而且考虑到现行的 LC_MONETARY 和 LC_NUMERIC
	 * 几乎可以肯定是“C”，因此恢复这些设置不应该失败。
	 */
#ifdef WIN32
	if (!setlocale(LC_CTYPE, save_lc_ctype))
		elog(FATAL, "failed to restore LC_CTYPE to \"%s\"", save_lc_ctype);
#endif
	if (!setlocale(LC_MONETARY, fc_save_lc_monetary))
		elog(FATAL, "failed to restore LC_MONETARY to \"%s\"", fc_save_lc_monetary);
	if (!setlocale(LC_NUMERIC, fc_save_lc_numeric))
		elog(FATAL, "failed to restore LC_NUMERIC to \"%s\"", fc_save_lc_numeric);

	/* 
	 * 在这一点上，我们已经尽力清理，并且可以在
	 * 清白的良心下调用可能抛出错误的函数。
	 * 但让我们确保在 worklconv 中不会泄漏任何已 strdup 的字段。
	 */
	PG_TRY();
	{
		int			fc_encoding;

		/* 释放 pstrdup 的区域名称 */
		pfree(fc_save_lc_monetary);
		pfree(fc_save_lc_numeric);
#ifdef WIN32
		pfree(save_lc_ctype);
#endif

		/* 如果之前的任何 strdup 调用失败，请立即投诉。 */
		if (!fc_struct_lconv_is_valid(&fc_worklconv))
			ereport(ERROR,
					(errcode(ERRCODE_OUT_OF_MEMORY),
					 errmsg("out of memory")));

		/*
		 * 现在我们必须将与区域设置相关的内容进行编码转换
		 * 到数据库编码。如果我们无法识别由 LC_NUMERIC 或 LC_MONETARY 隐含的
		 * 编码（即我们得到 -1），则使用 PG_SQL_ASCII，这将验证字符串
		 * 在数据库编码中是合法的。
		 */
		fc_encoding = pg_get_encoding_from_locale(locale_numeric, true);
		if (fc_encoding < 0)
			fc_encoding = PG_SQL_ASCII;

		fc_db_encoding_convert(fc_encoding, &fc_worklconv.decimal_point);
		fc_db_encoding_convert(fc_encoding, &fc_worklconv.thousands_sep);
		/* grouping 不是文本，不需要转换 */

		fc_encoding = pg_get_encoding_from_locale(locale_monetary, true);
		if (fc_encoding < 0)
			fc_encoding = PG_SQL_ASCII;

		fc_db_encoding_convert(fc_encoding, &fc_worklconv.int_curr_symbol);
		fc_db_encoding_convert(fc_encoding, &fc_worklconv.currency_symbol);
		fc_db_encoding_convert(fc_encoding, &fc_worklconv.mon_decimal_point);
		fc_db_encoding_convert(fc_encoding, &fc_worklconv.mon_thousands_sep);
		/* mon_grouping 不是文本，不需要转换 */
		fc_db_encoding_convert(fc_encoding, &fc_worklconv.positive_sign);
		fc_db_encoding_convert(fc_encoding, &fc_worklconv.negative_sign);
	}
	PG_CATCH();
	{
		fc_free_struct_lconv(&fc_worklconv);
		PG_RE_THROW();
	}
	PG_END_TRY();

	/*
	 * 一切正常，所以保存结果。
	 */
	fc_CurrentLocaleConv = fc_worklconv;
	fc_CurrentLocaleConvAllocated = true;
	CurrentLocaleConvValid = true;
	return &fc_CurrentLocaleConv;
}

#ifdef WIN32
/*
 * 在 Windows 上，strftime() 返回的输出编码为 CP_ACP（计算机的默认
 * 操作系统代码页），可能与 SERVER_ENCODING 不同。这在日本版本的
 * Windows 中尤其重要，后者将使用我们不支持作为服务器编码的 SJIS 编码。
 *
 * 因此，与其使用 strftime()，不如使用 wcsftime() 返回宽字符
 * （内部为 UTF16）的值，然后转换为 UTF8，这是我们知道如何直接处理的。
 *
 * 注意，这仅影响此文件中调用 strftime() 的部分，这些调用用于获取
 * 区域感知字符串。后端的其他部分使用 pg_strftime()，该部分不具备区域感知能力，
 * 无需替换。
 */
static size_t strftime_win32(char *dst, size_t dstlen,
			   const char *format, const struct tm *tm)
{
	size_t		len;
	wchar_t		wformat[8];		/* 下面使用的格式需要 3 个字符 */
	wchar_t		wbuf[MAX_L10N_DATA];

	/*
	 * 获取格式字符串的 wchar_t 版本。我们在此文件中实际上只使用
	 * 普通 ASCII 格式，所以我们可以说它们是 UTF8。
	 */
	len = MultiByteToWideChar(CP_UTF8, 0, format, -1,
							  wformat, lengthof(wformat));
	if (len == 0)
		elog(ERROR, "could not convert format string from UTF-8: error code %lu",
			 GetLastError());

	len = wcsftime(wbuf, MAX_L10N_DATA, wformat, tm);
	if (len == 0)
	{
		/*
		 * wcsftime 失败，可能是由于结果无法放入
		 * MAX_L10N_DATA。返回 0 但 dst 的内容未指定。
		 */
		return 0;
	}

	len = WideCharToMultiByte(CP_UTF8, 0, wbuf, len, dst, dstlen - 1,
							  NULL, NULL);
	if (len == 0)
		elog(ERROR, "could not convert string to UTF-8: error code %lu",
			 GetLastError());

	dst[len] = '\0';

	return len;
}

/* 重定义 strftime() */
#define strftime(a,b,c,d) strftime_win32(a,b,c,d)
#endif							/* WIN32 */

/*
 * cache_locale_time() 的子例程。
 * 将给定字符串从编码 "encoding" 转换为数据库编码，并将
 * 结果存储在 *dst 中，替换任何先前的值。
 */
static void fc_cache_single_string(char **fc_dst, const char *fc_src, int fc_encoding)
{
	char	   *fc_ptr;
	char	   *fc_olddst;

	/* 将字符串转换为数据库编码，或验证它是合法的 */
	fc_ptr = pg_any_to_server(fc_src, strlen(fc_src), fc_encoding);

	/* 将字符串存储在长生命周期存储中，替换任何先前的值 */
	fc_olddst = *fc_dst;
	*fc_dst = MemoryContextStrdup(TopMemoryContext, fc_ptr);
	if (fc_olddst)
		pfree(fc_olddst);

	/* 也可以清理任何 palloc 的转换结果 */
	if (fc_ptr != fc_src)
		pfree(fc_ptr);
}

/*
 * 如果需要，更新 lc_time 本地化缓存变量。
 */
void cache_locale_time(void)
{
	char		fc_buf[(2 * 7 + 2 * 12) * MAX_L10N_DATA];
	char	   *fc_bufptr;
	time_t		fc_timenow;
	struct tm  *fc_timeinfo;
	bool		fc_strftimefail = false;
	int			fc_encoding;
	int			fc_i;
	char	   *fc_save_lc_time;
#ifdef WIN32
	char	   *save_lc_ctype;
#endif

	/* 我们之前做过这个吗？ */
	if (CurrentLCTimeValid)
		return;

	elog(DEBUG3, "cache_locale_time() executed; locale: \"%s\"", locale_time);

	/*
	 * 正如在 PGLC_localeconv() 中一样，至关重要的是，在
	 * libc 的区域设置具有非默认值时我们不要抛出错误。因此，我们
	 * 只在关键部分调用 strftime()，然后在之后转换并保存其
	 * 结果。
	 */

	/* 保存当前的时间区域设置值 */
	fc_save_lc_time = setlocale(LC_TIME, NULL);
	if (!fc_save_lc_time)
		elog(ERROR, "setlocale(NULL) failed");
	fc_save_lc_time = pstrdup(fc_save_lc_time);

#ifdef WIN32

	/*
	 * 在 Windows 上，wcsftime() 内部似乎使用 LC_CTYPE，因此我们
	 * 必须在此处进行设置。此代码看起来与 PGLC_localeconv()
	 * 做的相同，但其根本原因不同：这并不确定我们将从 strftime_win32()
	 * 获取的编码。
	 */

	/* 保存 ctype 区域的当前值 */
	save_lc_ctype = setlocale(LC_CTYPE, NULL);
	if (!save_lc_ctype)
		elog(ERROR, "setlocale(NULL) failed");
	save_lc_ctype = pstrdup(save_lc_ctype);

	/* 使用 lc_time 设置 ctype */
	setlocale(LC_CTYPE, locale_time);
#endif

	setlocale(LC_TIME, locale_time);

	/* 我们使用接近当前时间的时间作为 strftime() 的数据。 */
	fc_timenow = time(NULL);
	fc_timeinfo = localtime(&fc_timenow);

	/* 将 strftime 结果存储在 buf[] 的 MAX_L10N_DATA 大小的部分中 */
	fc_bufptr = fc_buf;

	/*
	 * MAX_L10N_DATA 对于所有已知区域设置来说都是足够的缓冲区空间，
	 * POSIX 并未定义 strftime() 错误。（缓冲区空间耗尽并不是一个
	 * 错误。）实现可能通过返回 0（或更不可能的负值）并设置 errno
	 * 来报告错误。无论如何都报告 errno，以防实现这样做，但在调用之前
	 * 清除它，以便我们不会发出过时且无关的 errno。
	 */
	errno = 0;

	/* 本地化的星期几 */
	for (fc_i = 0; fc_i < 7; fc_i++)
	{
		fc_timeinfo->tm_wday = fc_i;
		if (strftime(fc_bufptr, MAX_L10N_DATA, "%a", fc_timeinfo) <= 0)
			fc_strftimefail = true;
		fc_bufptr += MAX_L10N_DATA;
		if (strftime(fc_bufptr, MAX_L10N_DATA, "%A", fc_timeinfo) <= 0)
			fc_strftimefail = true;
		fc_bufptr += MAX_L10N_DATA;
	}

	/* 本地化的月份 */
	for (fc_i = 0; fc_i < 12; fc_i++)
	{
		fc_timeinfo->tm_mon = fc_i;
		fc_timeinfo->tm_mday = 1;	/* 确保我们没有无效的日期 */
		if (strftime(fc_bufptr, MAX_L10N_DATA, "%b", fc_timeinfo) <= 0)
			fc_strftimefail = true;
		fc_bufptr += MAX_L10N_DATA;
		if (strftime(fc_bufptr, MAX_L10N_DATA, "%B", fc_timeinfo) <= 0)
			fc_strftimefail = true;
		fc_bufptr += MAX_L10N_DATA;
	}

	/*
	 * 在此时，我们已经尽力清理，因此可以自信地抛出错误，或
	 * 调用可能抛出错误的函数。
	 */
#ifdef WIN32
	if (!setlocale(LC_CTYPE, save_lc_ctype))
		elog(FATAL, "failed to restore LC_CTYPE to \"%s\"", save_lc_ctype);
#endif
	if (!setlocale(LC_TIME, fc_save_lc_time))
		elog(FATAL, "failed to restore LC_TIME to \"%s\"", fc_save_lc_time);

	/*
	 * 正如在 PGLC_localeconv() 中一样，我们必须将 strftime() 的输出
	 * 从 LC_TIME 隐含的编码转换为数据库编码。如果我们无法识别
	 * LC_TIME 编码，只需进行编码验证。
	 */
	if (fc_strftimefail)
		elog(ERROR, "strftime() failed: %m");

	/* 释放 pstrdup 的区域名称 */
	pfree(fc_save_lc_time);
#ifdef WIN32
	pfree(save_lc_ctype);
#endif

#ifndef WIN32

	/*
	 * 在 Windows 上，strftime_win32() 始终返回 UTF8 数据，因此如有必要
	 * 进行转换。
	 */
	fc_encoding = pg_get_encoding_from_locale(locale_time, true);
	if (fc_encoding < 0)
		fc_encoding = PG_SQL_ASCII;

#else

	/*
	 * 在 Windows 上，strftime_win32() 始终返回 UTF8 数据，因此根据需要进行转换。
	 */
	fc_encoding = PG_UTF8;

#endif							/* WIN32 */

	fc_bufptr = fc_buf;

	/* 本地化的星期几 */
	for (fc_i = 0; fc_i < 7; fc_i++)
	{
		fc_cache_single_string(&localized_abbrev_days[fc_i], fc_bufptr, fc_encoding);
		fc_bufptr += MAX_L10N_DATA;
		fc_cache_single_string(&localized_full_days[fc_i], fc_bufptr, fc_encoding);
		fc_bufptr += MAX_L10N_DATA;
	}
	localized_abbrev_days[7] = NULL;
	localized_full_days[7] = NULL;

	/* 本地化的月份 */
	for (fc_i = 0; fc_i < 12; fc_i++)
	{
		fc_cache_single_string(&localized_abbrev_months[fc_i], fc_bufptr, fc_encoding);
		fc_bufptr += MAX_L10N_DATA;
		fc_cache_single_string(&localized_full_months[fc_i], fc_bufptr, fc_encoding);
		fc_bufptr += MAX_L10N_DATA;
	}
	localized_abbrev_months[12] = NULL;
	localized_full_months[12] = NULL;

	CurrentLCTimeValid = true;
}


#if defined(WIN32) && defined(LC_MESSAGES)
/*
 * 将Windows的setlocale()参数转换为Unix风格。
 *
 * 无论平台如何，我们都在Unix风格的
 * LL[_CC][.ENCODING][@VARIANT]命名约定下安装消息目录。 只有遵循该样式的LC_MESSAGES设置
 * 才会引出本地化接口字符串。
 *
 * 在Visual Studio 2012之前（msvcr110.dll），Windows的setlocale()接受"C"
 * （但不接受"c"）和形式为<Language>[_<Country>][.<CodePage>]的字符串，
 * 不区分大小写。 setlocale()返回完全限定的形式；例如，setlocale("thaI")返回"Thai_Thailand.874"。
 * 在内部，setlocale()和_create_locale()选择一个"区域标识符"[1]并将其存储在一个未记录的_locale_t字段中。
 * 从该LCID，我们可以检索ISO 639语言和ISO 3166国家。字符编码无关紧要，
 * 因为服务器和客户端编码决定了这一点。
 *
 * Windows Vista引入了"区域名称"概念[2]，严格遵循RFC 4646。
 * 区域标识符现在已被弃用。从Visual Studio 2012开始，setlocale()除了接受历史上已接受的字符串外，还接受区域名称。
 * 它不会将其标准化；setlocale("Th-tH")返回"Th-tH"。setlocale(category, "")仍然返回一个传统
 * 字符串。此外，msvcr110.dll改变了未记录的_locale_t内容以承载区域名称而不是区域标识符。
 *
 * Visual Studio 2015仍应能与Visual Studio 2012执行相同操作，但_locale_t中缺少locale_name的声明，
 * 导致该代码编译失败，因此它改为使用EnumSystemLocalesEx枚举所有系统区域以找到所需的区域名称。
 * 如果输入参数是Unix风格的，那么我们可以通过使用GetLocaleInfoEx()并将LCType设置为LOCALE_SNAME直接
 * 获取ISO区域名称。
 *
 * MinGW头文件声明了_create_locale()，但msvcrt.dll在Windows 8之前的版本中缺少该符号。
 * IsoLocaleName()在MinGW构建的postgres.exe中始终失败，因此只有lc_messages GUC的Unix风格值才能引出
 * 本地化消息。特别是，initdb可以自动选择的每个lc_messages设置将仅生成C区域消息。
 * XXX可以通过将完全限定的区域名称运行通过查找表来解决此问题。
 *
 * 此函数返回指向一个静态缓冲区的指针，该缓冲区包含转换后的名称，如果转换失败，则返回NULL。
 *
 * [1] https://docs.microsoft.com/en-us/windows/win32/intl/locale-identifiers
 * [2] https://docs.microsoft.com/en-us/windows/win32/intl/locale-names
 */

#if _MSC_VER >= 1900
/*
 * EnumSystemLocalesEx()在get_iso_localename()中的回调函数。
 *
 * 此函数枚举所有系统区域，搜索一个与输入匹配的区域，格式为：
 * <Language>[_<Country>]，例如
 * English[_United States]
 *
 * 输入是一个三维wchar_t数组作为LPARAM。第一个元素是我们想匹配的locale_name，
 * 第二个元素是一个分配的缓冲区，如果找到匹配项，则将Unix风格的区域复制到此，
 * 第三个元素是搜索状态，如果找到匹配项则为1，否则为0。
 */
static BOOL CALLBACK
search_locale_enum(LPWSTR pStr, DWORD dwFlags, LPARAM lparam)
{
	wchar_t		test_locale[LOCALE_NAME_MAX_LENGTH];
	wchar_t   **argv;

	(void) (dwFlags);

	argv = (wchar_t **) lparam;
	*argv[2] = (wchar_t) 0;

	memset(test_locale, 0, sizeof(test_locale));

	/* 获取语言的英语名称 */
	if (GetLocaleInfoEx(pStr, LOCALE_SENGLISHLANGUAGENAME,
						test_locale, LOCALE_NAME_MAX_LENGTH))
	{
		/*
		 * 如果枚举的区域没有连字符("en")或者
		 * lc_message输入没有下划线("English")，我们只需比较<Language>标签。
		 */
		if (wcsrchr(pStr, '-') == NULL || wcsrchr(argv[0], '_') == NULL)
		{
			if (_wcsicmp(argv[0], test_locale) == 0)
			{
				wcscpy(argv[1], pStr);
				*argv[2] = (wchar_t) 1;
				return FALSE;
			}
		}

		/*
		 * 我们必须比较完整的<Language>_<Country>标签，因此我们追加
		 * 下划线和国家/地区的英文名称，例如
		 * "English_United States"。
		 */
		else
		{
			size_t		len;

			wcscat(test_locale, L"_");
			len = wcslen(test_locale);
			if (GetLocaleInfoEx(pStr, LOCALE_SENGLISHCOUNTRYNAME,
								test_locale + len,
								LOCALE_NAME_MAX_LENGTH - len))
			{
				if (_wcsicmp(argv[0], test_locale) == 0)
				{
					wcscpy(argv[1], pStr);
					*argv[2] = (wchar_t) 1;
					return FALSE;
				}
			}
		}
	}

	return TRUE;
}

/*
 * 此函数将Windows区域名称转换为用于Visual Studio 2015或更高版本的ISO格式版本。
 *
 * 如果没有找到有效的转换，则返回NULL。
 */
static char * get_iso_localename(const char *winlocname)
{
	wchar_t		wc_locale_name[LOCALE_NAME_MAX_LENGTH];
	wchar_t		buffer[LOCALE_NAME_MAX_LENGTH];
	static char iso_lc_messages[LOCALE_NAME_MAX_LENGTH];
	char	   *period;
	int			len;
	int			ret_val;

	/*
	 * 有效区域具有以下语法：
	 * <Language>[_<Country>[.<CodePage>]]
	 *
	 * GetLocaleInfoEx只能接受没有代码页的区域名称，并且出于此API的目的，代码页无关紧要。
	 */
	period = strchr(winlocname, '.');
	if (period != NULL)
		len = period - winlocname;
	else
		len = pg_mbstrlen(winlocname);

	memset(wc_locale_name, 0, sizeof(wc_locale_name));
	memset(buffer, 0, sizeof(buffer));
	MultiByteToWideChar(CP_ACP, 0, winlocname, len, wc_locale_name,
						LOCALE_NAME_MAX_LENGTH);

	/*
	 * 如果lc_messages已经是Unix风格字符串，我们与LOCALE_SNAME有直接匹配，例如en-US，en_US。
	 */
	ret_val = GetLocaleInfoEx(wc_locale_name, LOCALE_SNAME, (LPWSTR) &buffer,
							  LOCALE_NAME_MAX_LENGTH);
	if (!ret_val)
	{
		/*
		 * 在系统中搜索与语言和国家名称匹配的区域。
		 */
		wchar_t    *argv[3];

		argv[0] = wc_locale_name;
		argv[1] = buffer;
		argv[2] = (wchar_t *) &ret_val;
		EnumSystemLocalesEx(search_locale_enum, LOCALE_WINDOWS, (LPARAM) argv,
							NULL);
	}

	if (ret_val)
	{
		size_t		rc;
		char	   *hyphen;

		/* 区域名称仅使用ASCII，任何转换区域均可。 */
		rc = wchar2char(iso_lc_messages, buffer, sizeof(iso_lc_messages), NULL);
		if (rc == -1 || rc == sizeof(iso_lc_messages))
			return NULL;

		/*
		 * 只需用下划线替换连字符。请参阅
		 * IsoLocaleName中的评论。
		 */
		hyphen = strchr(iso_lc_messages, '-');
		if (hyphen)
			*hyphen = '_';
		return iso_lc_messages;
	}

	return NULL;
}
#endif							/* _MSC_VER >= 1900 */

static char * IsoLocaleName(const char *winlocname)
{
#if defined(_MSC_VER)
	static char iso_lc_messages[LOCALE_NAME_MAX_LENGTH];

	if (pg_strcasecmp("c", winlocname) == 0 ||
		pg_strcasecmp("posix", winlocname) == 0)
	{
		strcpy(iso_lc_messages, "C");
		return iso_lc_messages;
	}
	else
	{
#if (_MSC_VER >= 1900)			/* Visual Studio 2015或更高版本 */
		return get_iso_localename(winlocname);
#else
		_locale_t	loct;

		loct = _create_locale(LC_CTYPE, winlocname);
		if (loct != NULL)
		{
			size_t		rc;
			char	   *hyphen;

			/* 区域名称仅使用ASCII，任何转换区域均可。 */
			rc = wchar2char(iso_lc_messages, loct->locinfo->locale_name[LC_CTYPE],
							sizeof(iso_lc_messages), NULL);
			_free_locale(loct);
			if (rc == -1 || rc == sizeof(iso_lc_messages))
				return NULL;

			
/*
			 * 由于消息目录位于不区分大小写的
			 * 文件系统上，我们无需在此标准化字母大小写。只要
			 * 我们不会发布对此有影响的消息目录，
			 * 我们也无需翻译脚本/变体部分，例如
			 * uz-Cyrl-UZ 到 uz_UZ@cyrillic。只需将
			 * 连字符替换为下划线。
			 *
			 * 请注意，区域名称可以比我们在早期的 Visual Studio 版本中推导的值
			 * 更不具体。例如，French_France.1252 仅生成 "fr"。这并不
			 * 影响截至目前可用的任何国家特定消息目录（pt_BR，zh_CN，zh_TW）。
			 */
			hyphen = strchr(iso_lc_messages, '-');
			if (hyphen)
				*hyphen = '_';
			return iso_lc_messages;
		}
#endif							/* Visual Studio 2015或更高版本 */
	}
#endif							/* 定义(_MSC_VER) */
	return NULL;				/* 在此版本的 msvc/mingw 上不支持 */
}
#endif							/* WIN32 && LC_MESSAGES */


/*
 * 检测老化的 strxfrm() 实现，在部分区域中，
 * 超过指定的缓冲区长度。受影响的用户必须在使用
 * PostgreSQL 9.5 或更高版本之前更新操作系统软件包。
 *
 * 假设该 bug 在不同的主服务器启动中可能会出现或消失
 * 由于不同机器之间的物理复制。假设该 bug 的
 * 存在不会在特定主服务器的生命周期内发生变化。根据
 * 这些假设，每个 LC_COLLATE 设置的每个主服务器启动
 * 至少调用一次该函数。目前没有已知受影响的系统提供 strxfrm_l()，
 * 因此不需要考虑 pg_collation 地区。
 */
void check_strxfrm_bug(void)
{
	char		fc_buf[32];
	const int	fc_canary = 0x7F;
	bool		fc_ok = true;

	/*
	 * 给定一个两字节的 ASCII 字符串和长度限制 7、8 或 9，Solaris 10
	 * 05/08 返回 18，并修改 10 字节。它尊重这个范围
	 * 以上或以下的限制。
	 *
	 * Solaris 8 也存在该 bug；在 Solaris 10
	 * 01/13 和 Solaris 11.2 中不存在。受影响的区域包括 is_IS.ISO8859-1，
	 * en_US.UTF-8，en_US.ISO8859-1 和 ru_RU.KOI8-R。未受影响的区域
	 * 包括 de_DE.UTF-8，de_DE.ISO8859-1，zh_TW.UTF-8 和 C。
	 */
	fc_buf[7] = fc_canary;
	(void) strxfrm(fc_buf, "ab", 7);
	if (fc_buf[7] != fc_canary)
		fc_ok = false;

	/*
	 * illumos bug #1594 从 2010-10-11 到
	 * 2012-02-01 在源代码树中存在。给定任意长度的 ASCII 字符串和长度限制 1，
	 * 受影响的系统忽略长度限制并修改的字节数
	 * 少于返回值 1。此 bug 的问题输入与
	 * Solaris bug 不重叠，因此有独立的测试。
	 *
	 * 受影响的系统包括 smartos-20110926T021612Z。受影响的区域
	 * 包括 en_US.ISO8859-1 和 en_US.UTF-8。未受影响的区域包括 C。
	 */
	fc_buf[1] = fc_canary;
	(void) strxfrm(fc_buf, "a", 1);
	if (fc_buf[1] != fc_canary)
		fc_ok = false;

	if (!fc_ok)
		ereport(ERROR,
				(errcode(ERRCODE_SYSTEM_ERROR),
				 errmsg_internal("strxfrm(), in locale \"%s\", writes past the specified array length",
								 setlocale(LC_COLLATE, NULL)),
				 errhint("Apply system library package updates.")));
}


/*
 * 排序信息的缓存机制。
 *
 * 我们缓存两个标志：排序的 LC_COLLATE 或 LC_CTYPE 是否为 C
 * （或 POSIX），从而可以在多个地方优化一些代码路径。
 * 对于内置的 C 和 POSIX 排序，我们无需执行缓存查找便可以知道这一点，
 * 但我们也希望支持 C/POSIX 的别名。
 * 对于 "默认" 排序，有单独的静态缓存变量，
 * 因为查询 pg_collation 目录并不能告诉我们我们需要的内容。
 *
 * 此外，如果为排序请求了 pg_locale_t，我们会在一个后端的生命周期内缓存它。
 *
 * 请注意，某些代码依赖于标志不报告假阴性
 * （即，表示它不是 C 但实际上是）。例如，如果区域为 C，则 char2wchar()
 * 可能会失败，因此 str_tolower() 在这种情况下不应调用它。
 *
 * 请注意，我们目前缺乏任何刷新缓存的方法。既然我们不
 * 支持 ALTER COLLATION，这没问题。最坏的情况是有人
 * 删除了一个排序，而在现有后端中留下了一个无用的缓存条目。
 */

static collation_cache_entry *
fc_lookup_collation_cache(Oid fc_collation, bool set_flags)
{
	collation_cache_entry *fc_cache_entry;
	bool		fc_found;

	Assert(OidIsValid(fc_collation));
	Assert(fc_collation != DEFAULT_COLLATION_OID);

	if (collation_cache == NULL)
	{
		/* 第一次初始化哈希表 */
		HASHCTL		fc_ctl;

		fc_ctl.keysize = sizeof(Oid);
		fc_ctl.entrysize = sizeof(collation_cache_entry);
		collation_cache = hash_create("Collation cache", 100, &fc_ctl,
									  HASH_ELEM | HASH_BLOBS);
	}

	fc_cache_entry = hash_search(collation_cache, &fc_collation, HASH_ENTER, &fc_found);
	if (!fc_found)
	{
		/*
		 * 确保缓存条目被标记为无效，以防在设置之前
		 * 失败。
		 */
		fc_cache_entry->flags_valid = false;
		fc_cache_entry->locale = 0;
	}

	if (set_flags && !fc_cache_entry->flags_valid)
	{
		/* 尝试设置标志 */
		HeapTuple	fc_tp;
		Form_pg_collation fc_collform;

		fc_tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(fc_collation));
		if (!HeapTupleIsValid(fc_tp))
			elog(ERROR, "cache lookup failed for collation %u", fc_collation);
		fc_collform = (Form_pg_collation) GETSTRUCT(fc_tp);

		if (fc_collform->collprovider == COLLPROVIDER_LIBC)
		{
			Datum		fc_datum;
			bool		fc_isnull;
			const char *fc_collcollate;
			const char *fc_collctype;

			fc_datum = SysCacheGetAttr(COLLOID, fc_tp, Anum_pg_collation_collcollate, &fc_isnull);
			Assert(!fc_isnull);
			fc_collcollate = TextDatumGetCString(fc_datum);
			fc_datum = SysCacheGetAttr(COLLOID, fc_tp, Anum_pg_collation_collctype, &fc_isnull);
			Assert(!fc_isnull);
			fc_collctype = TextDatumGetCString(fc_datum);

			fc_cache_entry->collate_is_c = ((strcmp(fc_collcollate, "C") == 0) ||
										 (strcmp(fc_collcollate, "POSIX") == 0));
			fc_cache_entry->ctype_is_c = ((strcmp(fc_collctype, "C") == 0) ||
									   (strcmp(fc_collctype, "POSIX") == 0));
		}
		else
		{
			fc_cache_entry->collate_is_c = false;
			fc_cache_entry->ctype_is_c = false;
		}

		fc_cache_entry->flags_valid = true;

		ReleaseSysCache(fc_tp);
	}

	return fc_cache_entry;
}


/*
 * 检测排序的 LC_COLLATE 属性是否为 C
 */
bool lc_collate_is_c(Oid fc_collation)
{
	/*
	 * 如果我们被询问 "排序 0"，返回 false，以便代码进入
	 * 非 C 路径并报告该排序是无效的。
	 */
	if (!OidIsValid(fc_collation))
		return false;

	/*
	 * 如果我们被询问默认排序，则必须向 C
	 * 库询问。缓存结果，以便我们只需计算一次。
	 */
	if (fc_collation == DEFAULT_COLLATION_OID)
	{
		static int	fc_result = -1;
		char	   *fc_localeptr;

		if (default_locale.provider == COLLPROVIDER_ICU)
			return false;

		if (fc_result >= 0)
			return (bool) fc_result;
		fc_localeptr = setlocale(LC_COLLATE, NULL);
		if (!fc_localeptr)
			elog(ERROR, "invalid LC_COLLATE setting");

		if (strcmp(fc_localeptr, "C") == 0)
			fc_result = true;
		else if (strcmp(fc_localeptr, "POSIX") == 0)
			fc_result = true;
		else
			fc_result = false;
		return (bool) fc_result;
	}

	/*
	 * 如果我们被询问内置的 C/POSIX 排序，我们知道。
	 */
	if (fc_collation == C_COLLATION_OID ||
		fc_collation == POSIX_COLLATION_OID)
		return true;

	/*
	 * 否则，我们必须查询 pg_collation，但我们缓存这一点。
	 */
	return (fc_lookup_collation_cache(fc_collation, true))->collate_is_c;
}

/*
 * 检测排序的 LC_CTYPE 属性是否为 C
 */
bool lc_ctype_is_c(Oid fc_collation)
{
	/*
	 * 如果我们被询问 "排序 0"，返回 false，以便代码进入
	 * 非 C 路径并报告该排序是无效的。
	 */
	if (!OidIsValid(fc_collation))
		return false;

	/*
	 * 如果我们被询问默认排序，则必须向 C
	 * 库询问。缓存结果，以便我们只需计算一次。
	 */
	if (fc_collation == DEFAULT_COLLATION_OID)
	{
		static int	fc_result = -1;
		char	   *fc_localeptr;

		if (default_locale.provider == COLLPROVIDER_ICU)
			return false;

		if (fc_result >= 0)
			return (bool) fc_result;
		fc_localeptr = setlocale(LC_CTYPE, NULL);
		if (!fc_localeptr)
			elog(ERROR, "invalid LC_CTYPE setting");

		if (strcmp(fc_localeptr, "C") == 0)
			fc_result = true;
		else if (strcmp(fc_localeptr, "POSIX") == 0)
			fc_result = true;
		else
			fc_result = false;
		return (bool) fc_result;
	}

	/*
	 * 如果我们被询问内置的 C/POSIX 排序，我们知道。
	 */
	if (fc_collation == C_COLLATION_OID ||
		fc_collation == POSIX_COLLATION_OID)
		return true;

	/*
	 * 否则，我们必须查询 pg_collation，但我们缓存这一点。
	 */
	return (fc_lookup_collation_cache(fc_collation, true))->ctype_is_c;
}

struct pg_locale_struct default_locale;

void make_icu_collator(const char *fc_iculocstr,
				  struct pg_locale_struct *fc_resultp)
{
#ifdef USE_ICU
	UCollator  *fc_collator;
	UErrorCode	fc_status;

	fc_status = U_ZERO_ERROR;
	fc_collator = ucol_open(fc_iculocstr, &fc_status);
	if (U_FAILURE(fc_status))
		ereport(ERROR,
				(errmsg("could not open collator for locale \"%s\": %s",
						fc_iculocstr, u_errorName(fc_status))));

	if (U_ICU_VERSION_MAJOR_NUM < 54)
		fc_icu_set_collation_attributes(fc_collator, fc_iculocstr);

	/* 我们会泄露这个字符串，如果调用者稍后出错 :-( */
	fc_resultp->info.icu.locale = MemoryContextStrdup(TopMemoryContext, fc_iculocstr);
	fc_resultp->info.icu.ucol = fc_collator;
#else							/* 不使用 USE_ICU */
	/* 如果通过使用 ICU 创建了一个排序规则，可能会到这里 */
	ereport(ERROR,
			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
			 errmsg("ICU is not supported in this build")));
#endif							/* 不使用 USE_ICU */
}


/* 用于报告来自 newlocale() 的错误的简单子例程 */
#ifdef HAVE_LOCALE_T
static void fc_report_newlocale_failure(const char *fc_localename)
{
	int			fc_save_errno;

	/*
	 * Windows 不提供来自 _create_locale() 的任何有用错误指示，
	 * 而 BSD 派生的平台似乎也不觉得需要设置 errno（尽管 POSIX 很明确
	 * newlocale 应该这样做）。因此，如果 errno 尚未设置，假设应报告 ENOENT。
	 */
	if (errno == 0)
		errno = ENOENT;

	/*
	 * ENOENT 意味着“没有这样的区域设置”，而不是“没有这样的文件”，所以
	 * 用 errdetail 消息来澄清 errno。
	 */
	fc_save_errno = errno;			/* 辅助函数可能会改变 errno */
	ereport(ERROR,
			(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
			 errmsg("could not create locale \"%s\": %m",
					fc_localename),
			 (fc_save_errno == ENOENT ?
			  errdetail("The operating system could not find any locale data for the locale name \"%s\".",
						fc_localename) : 0)));
}
#endif							/* HAVE_LOCALE_T */


/*
 * 从排序规则 OID 创建 locale_t。结果在后端的生命周期内被缓存。
 * 因此，不要用 freelocale() 释放结果。
 *
 * 作为特殊优化，默认/数据库排序规则返回 0。
 * 调用者应当转回到不支持 locale_t 的代码路径。
 * 此外，调用者应避免在下 C/POSIX 快速路径之前调用此方法，
 * 因为这样的快速路径应该即使在不支持 locale_t 的 C 库的平台上也能工作。
 *
 * 为了简便，我们总是生成 COLLATE + CTYPE，即使我们可能只需要其中之一。
 * 由于这个函数每个会话只被调用一次，它的成本不应太高。
 */
pg_locale_t
pg_newlocale_from_collation(Oid fc_collid)
{
	collation_cache_entry *fc_cache_entry;

	/* 调用者必须传递有效的 OID */
	Assert(OidIsValid(fc_collid));

	if (fc_collid == DEFAULT_COLLATION_OID)
	{
		if (default_locale.provider == COLLPROVIDER_ICU)
			return &default_locale;
		else
			return (pg_locale_t) 0;
	}

	fc_cache_entry = fc_lookup_collation_cache(fc_collid, false);

	if (fc_cache_entry->locale == 0)
	{
		/* 我们在此会话中还没有计算这个，因此计算它 */
		HeapTuple	fc_tp;
		Form_pg_collation fc_collform;
		struct pg_locale_struct fc_result;
		pg_locale_t fc_resultp;
		Datum		fc_datum;
		bool		fc_isnull;

		fc_tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(fc_collid));
		if (!HeapTupleIsValid(fc_tp))
			elog(ERROR, "cache lookup failed for collation %u", fc_collid);
		fc_collform = (Form_pg_collation) GETSTRUCT(fc_tp);

		/* 我们将在本地填充结果结构，然后分配内存 */
		memset(&fc_result, 0, sizeof(fc_result));
		fc_result.provider = fc_collform->collprovider;
		fc_result.deterministic = fc_collform->collisdeterministic;

		if (fc_collform->collprovider == COLLPROVIDER_LIBC)
		{
#ifdef HAVE_LOCALE_T
			const char *fc_collcollate;
			const char *fc_collctype pg_attribute_unused();
			locale_t	fc_loc;

			fc_datum = SysCacheGetAttr(COLLOID, fc_tp, Anum_pg_collation_collcollate, &fc_isnull);
			Assert(!fc_isnull);
			fc_collcollate = TextDatumGetCString(fc_datum);
			fc_datum = SysCacheGetAttr(COLLOID, fc_tp, Anum_pg_collation_collctype, &fc_isnull);
			Assert(!fc_isnull);
			fc_collctype = TextDatumGetCString(fc_datum);

			if (strcmp(fc_collcollate, fc_collctype) == 0)
			{
				/* 正常情况下它们是相同的 */
				errno = 0;
#ifndef WIN32
				fc_loc = newlocale(LC_COLLATE_MASK | LC_CTYPE_MASK, fc_collcollate,
								NULL);
#else
				fc_loc = _create_locale(LC_ALL, fc_collcollate);
#endif
				if (!fc_loc)
					fc_report_newlocale_failure(fc_collcollate);
			}
			else
			{
#ifndef WIN32
				/* 我们需要两个 newlocale() 步骤 */
				locale_t	fc_loc1;

				errno = 0;
				fc_loc1 = newlocale(LC_COLLATE_MASK, fc_collcollate, NULL);
#ifndef FDD//cppcheck
				if (!fc_loc1)
					fc_report_newlocale_failure(fc_collcollate);
				errno = 0;
				fc_loc = newlocale(LC_CTYPE_MASK, fc_collctype, fc_loc1);
				if (!fc_loc)
					fc_report_newlocale_failure(fc_collctype);
#else//cppcheck
				if (!fc_loc1)
				{
					fc_report_newlocale_failure(fc_collcollate);
					return NULL;
				}
				errno = 0;
				fc_loc = newlocale(LC_CTYPE_MASK, fc_collctype, fc_loc1);
				if (!fc_loc)
				{
					fc_report_newlocale_failure(fc_collctype);
					return NULL;
				}
#endif//cppcheck
#else

				/*
				 * XXX _create_locale() API 似乎不支持这个。
				 * 可能通过更改 pg_locale_t 以包含两个独立字段来解决。
				 */
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("collations with different collate and ctype values are not supported on this platform")));
#ifdef FDD//cppcheck
				return NULL;
#endif				
#endif
			}

			fc_result.info.lt = fc_loc;
#else							/* 不支持 HAVE_LOCALE_T */
			/* 不支持 locale_t 的平台 */
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("collation provider LIBC is not supported on this platform")));
#endif							/* 不支持 HAVE_LOCALE_T */
		}
		else if (fc_collform->collprovider == COLLPROVIDER_ICU)
		{
			const char *fc_iculocstr;

			fc_datum = SysCacheGetAttr(COLLOID, fc_tp, Anum_pg_collation_colliculocale, &fc_isnull);
			Assert(!fc_isnull);
			fc_iculocstr = TextDatumGetCString(fc_datum);
			make_icu_collator(fc_iculocstr, &fc_result);
		}

		fc_datum = SysCacheGetAttr(COLLOID, fc_tp, Anum_pg_collation_collversion,
								&fc_isnull);
		if (!fc_isnull)
		{
			char	   *fc_actual_versionstr;
			char	   *fc_collversionstr;

			fc_collversionstr = TextDatumGetCString(fc_datum);

			fc_datum = SysCacheGetAttr(COLLOID, fc_tp, fc_collform->collprovider == COLLPROVIDER_ICU ? Anum_pg_collation_colliculocale : Anum_pg_collation_collcollate, &fc_isnull);
			Assert(!fc_isnull);

			fc_actual_versionstr = get_collation_actual_version(fc_collform->collprovider,
															 TextDatumGetCString(fc_datum));
			if (!fc_actual_versionstr)
			{
				/*
				 * 当在 CREATE COLLATION 中指定一个版本，但提供者不支持版本控制，或者
				 * 手动在目录中创建了混乱时，可能发生这种情况。
				 */
				ereport(ERROR,
						(errmsg("collation \"%s\" has no actual version, but a version was recorded",
								NameStr(fc_collform->collname))));
#ifdef FDD//cppcheck
				return NULL;
#endif
			}

			if (strcmp(fc_actual_versionstr, fc_collversionstr) != 0)
				ereport(WARNING,
						(errmsg("collation \"%s\" has version mismatch",
								NameStr(fc_collform->collname)),
						 errdetail("The collation in the database was created using version %s, "
								   "but the operating system provides version %s.",
								   fc_collversionstr, fc_actual_versionstr),
						 errhint("Rebuild all objects affected by this collation and run "
								 "ALTER COLLATION %s REFRESH VERSION, "
								 "or build PostgreSQL with the right library version.",
								 quote_qualified_identifier(get_namespace_name(fc_collform->collnamespace),
															NameStr(fc_collform->collname)))));
		}

		ReleaseSysCache(fc_tp);

		/* 我们将在 TopMemoryContext 中保留 pg_locale_t 结构 */
		fc_resultp = MemoryContextAlloc(TopMemoryContext, sizeof(*fc_resultp));
		*fc_resultp = fc_result;

		fc_cache_entry->locale = fc_resultp;
	}

	return fc_cache_entry->locale;
}

/*
 * 从操作系统/库中获取给定排序规则的提供者特定排序规则版本字符串。
 */
char * get_collation_actual_version(char fc_collprovider, const char *fc_collcollate)
{
	char	   *fc_collversion = NULL;

#ifdef USE_ICU
	if (fc_collprovider == COLLPROVIDER_ICU)
	{
		UCollator  *fc_collator;
		UErrorCode	fc_status;
		UVersionInfo fc_versioninfo;
		char		fc_buf[U_MAX_VERSION_STRING_LENGTH];

		fc_status = U_ZERO_ERROR;
		fc_collator = ucol_open(fc_collcollate, &fc_status);
		if (U_FAILURE(fc_status))
			ereport(ERROR,
					(errmsg("could not open collator for locale \"%s\": %s",
							fc_collcollate, u_errorName(fc_status))));
		ucol_getVersion(fc_collator, fc_versioninfo);
		ucol_close(fc_collator);

		u_versionToString(fc_versioninfo, fc_buf);
		fc_collversion = pstrdup(fc_buf);
	}
	else
#endif
		if (fc_collprovider == COLLPROVIDER_LIBC &&
			pg_strcasecmp("C", fc_collcollate) != 0 &&
			pg_strncasecmp("C.", fc_collcollate, 2) != 0 &&
			pg_strcasecmp("POSIX", fc_collcollate) != 0)
	{
#if defined(__GLIBC__)
		/* 使用 glibc 版本，因为我们没有更好的东西。 */
		fc_collversion = pstrdup(gnu_get_libc_version());
#elif defined(LC_VERSION_MASK)
		locale_t	fc_loc;

		/* 查找 FreeBSD 排序规则版本。 */
		fc_loc = newlocale(LC_COLLATE_MASK, fc_collcollate, NULL);
		if (fc_loc)
		{
			fc_collversion =
				pstrdup(querylocale(LC_COLLATE_MASK | LC_VERSION_MASK, fc_loc));
			freelocale(fc_loc);
		}
		else
			ereport(ERROR,
					(errmsg("could not load locale \"%s\"", fc_collcollate)));
#elif defined(WIN32) && _WIN32_WINNT >= 0x0600
		/*
		 * 如果我们的目标是 Windows Vista 及以上版本，我们可以要求根据排序规则名称获取名称
		 * （早期版本需要一个地点代码，我们没有）。
		 */
		NLSVERSIONINFOEX fc_version = {sizeof(NLSVERSIONINFOEX)};
		WCHAR		fc_wide_collcollate[LOCALE_NAME_MAX_LENGTH];

		MultiByteToWideChar(CP_ACP, 0, fc_collcollate, -1, fc_wide_collcollate,
							LOCALE_NAME_MAX_LENGTH);
		if (!GetNLSVersionEx(COMPARE_STRING, fc_wide_collcollate, &fc_version))
		{
			/*
			 * GetNLSVersionEx() 需要一个语言标签，例如“en-US”，而不是像
			 * “English_United States.1252”这样的区域设置名称。直到这些
			 * 值可以被防止进入系统，或者 100%
			 * 可靠地转换为更有用的标签格式，容忍由此产生的错误，并报告我们没有版本数据。
			 */
			if (GetLastError() == ERROR_INVALID_PARAMETER)
				return NULL;

			ereport(ERROR,
					(errmsg("could not get collation version for locale \"%s\": error code %lu",
							fc_collcollate,
							GetLastError())));
		}
		fc_collversion = psprintf("%lu.%lu,%lu.%lu",
							   (fc_version.dwNLSVersion >> 8) & 0xFFFF,
							   fc_version.dwNLSVersion & 0xFF,
							   (fc_version.dwDefinedVersion >> 8) & 0xFFFF,
							   fc_version.dwDefinedVersion & 0xFF);
#endif
	}

	return fc_collversion;
}


#ifdef USE_ICU
/*
 * 用于在 ICU 的 UChar 字符串与数据库编码中的 C 字符串之间转换的转换器对象。
 * 由于数据库编码不变，我们每个会话只需要一个这样的对象。
 */
static UConverter *icu_converter = NULL;

static void fc_init_icu_converter(void)
{
	const char *fc_icu_encoding_name;
	UErrorCode	fc_status;
	UConverter *fc_conv;

	if (icu_converter)
		return;					/* 已经完成 */

	fc_icu_encoding_name = get_encoding_name_for_icu(GetDatabaseEncoding());
	if (!fc_icu_encoding_name)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("encoding \"%s\" not supported by ICU",
						pg_encoding_to_char(GetDatabaseEncoding()))));

	fc_status = U_ZERO_ERROR;
	fc_conv = ucnv_open(fc_icu_encoding_name, &fc_status);
	if (U_FAILURE(fc_status))
		ereport(ERROR,
				(errmsg("could not open ICU converter for encoding \"%s\": %s",
						fc_icu_encoding_name, u_errorName(fc_status))));

	icu_converter = fc_conv;
}

/*
 * 将数据库编码中的字符串转换为 UChars 字符串。
 *
 * buff 中的源字符串长度为 nbytes
 * （无需以 nul 结尾）
 *
 * *buff_uchar 接收指向分配结果字符串的指针，函数结果为生成的 UChars 数量。
 *
 * 结果字符串以 nul 结尾，尽管大多数调用者更依赖结果长度。
 */
int32_t
icu_to_uchar(UChar **fc_buff_uchar, const char *fc_buff, size_t fc_nbytes)
{
	UErrorCode	fc_status;
	int32_t		fc_len_uchar;

	fc_init_icu_converter();

	fc_status = U_ZERO_ERROR;
	fc_len_uchar = ucnv_toUChars(icu_converter, NULL, 0,
							  fc_buff, fc_nbytes, &fc_status);
	if (U_FAILURE(fc_status) && fc_status != U_BUFFER_OVERFLOW_ERROR)
		ereport(ERROR,
				(errmsg("%s failed: %s", "ucnv_toUChars", u_errorName(fc_status))));

	*fc_buff_uchar = palloc((fc_len_uchar + 1) * sizeof(**fc_buff_uchar));

	fc_status = U_ZERO_ERROR;
	fc_len_uchar = ucnv_toUChars(icu_converter, *fc_buff_uchar, fc_len_uchar + 1,
							  fc_buff, fc_nbytes, &fc_status);
	if (U_FAILURE(fc_status))
		ereport(ERROR,
				(errmsg("%s failed: %s", "ucnv_toUChars", u_errorName(fc_status))));

	return fc_len_uchar;
}

/*
 * 将 UChars 字符串转换为数据库编码。
 *
 * buff_uchar 中的源字符串长度为 len_uchar
 * （无需以 nul 结尾）
 *
 * *result 接收指向分配结果字符串的指针，函数结果为生成的字节数（不包括 nul）。
 *
 * 结果字符串以 nul 结尾。
 */
int32_t
icu_from_uchar(char **fc_result, const UChar *fc_buff_uchar, int32_t fc_len_uchar)
{
	UErrorCode	fc_status;
	int32_t		fc_len_result;

	fc_init_icu_converter();

	fc_status = U_ZERO_ERROR;
	fc_len_result = ucnv_fromUChars(icu_converter, NULL, 0,
								 fc_buff_uchar, fc_len_uchar, &fc_status);
	if (U_FAILURE(fc_status) && fc_status != U_BUFFER_OVERFLOW_ERROR)
		ereport(ERROR,
				(errmsg("%s failed: %s", "ucnv_fromUChars",
						u_errorName(fc_status))));

	*fc_result = palloc(fc_len_result + 1);

	fc_status = U_ZERO_ERROR;
	fc_len_result = ucnv_fromUChars(icu_converter, *fc_result, fc_len_result + 1,
								 fc_buff_uchar, fc_len_uchar, &fc_status);
	if (U_FAILURE(fc_status))
		ereport(ERROR,
				(errmsg("%s failed: %s", "ucnv_fromUChars",
						u_errorName(fc_status))));

	return fc_len_result;
}

/*
 * 解析排序规则属性并将其应用于打开的排序器。这个方法接受
 * 一个字符串例如“und@colStrength=primary;colCaseLevel=yes”，并解析
 * 并应用键值参数。
 *
 * 从 ICU 版本 54 开始，属性由 ucol_open() 自动处理，因此
 * 仅在较旧版本中模拟此行为时是必要的。
 */
pg_attribute_unused()
static void fc_icu_set_collation_attributes(UCollator *fc_collator, const char *fc_loc)
{
	char	   *fc_str = asc_tolower(fc_loc, strlen(fc_loc));

	fc_str = strchr(fc_str, '@');
	if (!fc_str)
		return;
	fc_str++;

	for (char *fc_token = strtok(fc_str, ";"); fc_token; fc_token = strtok(NULL, ";"))
	{
		char	   *fc_e = strchr(fc_token, '=');

		if (fc_e)
		{
			char	   *fc_name;
			char	   *fc_value;
			UColAttribute fc_uattr;
			UColAttributeValue fc_uvalue;
			UErrorCode	fc_status;

			fc_status = U_ZERO_ERROR;

			*fc_e = '\0';
			fc_name = fc_token;
			fc_value = fc_e + 1;

			/*
			 * 查看 ICU i18n/coll.cpp 中的属性名称和值列表
			 */
			if (strcmp(fc_name, "colstrength") == 0)
				fc_uattr = UCOL_STRENGTH;
			else if (strcmp(fc_name, "colbackwards") == 0)
				fc_uattr = UCOL_FRENCH_COLLATION;
			else if (strcmp(fc_name, "colcaselevel") == 0)
				fc_uattr = UCOL_CASE_LEVEL;
			else if (strcmp(fc_name, "colcasefirst") == 0)
				fc_uattr = UCOL_CASE_FIRST;
			else if (strcmp(fc_name, "colalternate") == 0)
				fc_uattr = UCOL_ALTERNATE_HANDLING;
			else if (strcmp(fc_name, "colnormalization") == 0)
				fc_uattr = UCOL_NORMALIZATION_MODE;
			else if (strcmp(fc_name, "colnumeric") == 0)
				fc_uattr = UCOL_NUMERIC_COLLATION;
			else
				/* 如果未知则忽略 */
				continue;

			if (strcmp(fc_value, "primary") == 0)
				fc_uvalue = UCOL_PRIMARY;
			else if (strcmp(fc_value, "secondary") == 0)
				fc_uvalue = UCOL_SECONDARY;
			else if (strcmp(fc_value, "tertiary") == 0)
				fc_uvalue = UCOL_TERTIARY;
			else if (strcmp(fc_value, "quaternary") == 0)
				fc_uvalue = UCOL_QUATERNARY;
			else if (strcmp(fc_value, "identical") == 0)
				fc_uvalue = UCOL_IDENTICAL;
			else if (strcmp(fc_value, "no") == 0)
				fc_uvalue = UCOL_OFF;
			else if (strcmp(fc_value, "yes") == 0)
				fc_uvalue = UCOL_ON;
			else if (strcmp(fc_value, "shifted") == 0)
				fc_uvalue = UCOL_SHIFTED;
			else if (strcmp(fc_value, "non-ignorable") == 0)
				fc_uvalue = UCOL_NON_IGNORABLE;
			else if (strcmp(fc_value, "lower") == 0)
				fc_uvalue = UCOL_LOWER_FIRST;
			else if (strcmp(fc_value, "upper") == 0)
				fc_uvalue = UCOL_UPPER_FIRST;
			else
				fc_status = U_ILLEGAL_ARGUMENT_ERROR;

			if (fc_status == U_ZERO_ERROR)
				ucol_setAttribute(fc_collator, fc_uattr, fc_uvalue, &fc_status);

			/*
			 * 假设错误来自 ucol_open()，以便在不同的 ICU 版本中
			 * 一致的错误信息。
			 */
			if (U_FAILURE(fc_status))
				ereport(ERROR,
						(errmsg("could not open collator for locale \"%s\": %s",
								fc_loc, u_errorName(fc_status))));
		}
	}
}

#endif							/* USE_ICU */

/*
 * 检查给定的区域设置 ID 是否有效，如果无效则报告错误。
 */
void check_icu_locale(const char *fc_icu_locale)
{
#ifdef USE_ICU
	UCollator  *fc_collator;
	UErrorCode	fc_status;

	fc_status = U_ZERO_ERROR;
	fc_collator = ucol_open(fc_icu_locale, &fc_status);
	if (U_FAILURE(fc_status))
		ereport(ERROR,
				(errmsg("could not open collator for locale \"%s\": %s",
						fc_icu_locale, u_errorName(fc_status))));

	if (U_ICU_VERSION_MAJOR_NUM < 54)
		fc_icu_set_collation_attributes(fc_collator, fc_icu_locale);
	ucol_close(fc_collator);
#else
	ereport(ERROR,
			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
			 errmsg("ICU is not supported in this build")));
#endif
}

/*
 * 这些函数从 libc 的 wchar_t 转换到 / 从 pg_wchar_t，*而不是* pg_wchar_t。
 * 因此我们将它们保留在这里，而不是与 mbutils 代码放在一起。
 */

/*
 * wchar2char --- 将宽字符转换为多字节格式
 *
 * 这具有与标准的 wcstombs_l() 函数相同的 API；特别是，
 * tolen 是存储在 *to 的最大字节数，而 *from 必须为
 * 以零结尾。输出将以零结尾，前提是有足够空间。
 */
size_t wchar2char(char *fc_to, const wchar_t *fc_from, size_t fc_tolen, pg_locale_t fc_locale)
{
	size_t		fc_result;

	Assert(!fc_locale || fc_locale->provider == COLLPROVIDER_LIBC);

	if (fc_tolen == 0)
		return 0;

#ifdef WIN32

	/*
	 * 在 Windows 系统中，“Unicode”区域假定使用 UTF16 而不是 UTF8 编码，并且
	 * 出于某种原因，mbstowcs 和 wcstombs 不会为我们处理这个问题，所以我们使用
	 * MultiByteToWideChar()。
	 */
	if (GetDatabaseEncoding() == PG_UTF8)
	{
		fc_result = WideCharToMultiByte(CP_UTF8, 0, from, -1, fc_to, fc_tolen,
									 NULL, NULL);
		/* 返回零表示失败 */
		if (fc_result <= 0)
			fc_result = -1;
		else
		{
			Assert(fc_result <= fc_tolen);
			/* 微软在结果中计算零终止符 */
			fc_result--;
		}
	}
	else
#endif							/* WIN32 */
	if (fc_locale == (pg_locale_t) 0)
	{
		/* 对于默认区域设置直接使用 wcstombs */
		fc_result = wcstombs(fc_to, fc_from, fc_tolen);
	}
	else
	{
#ifdef HAVE_LOCALE_T
#ifdef HAVE_WCSTOMBS_L
		/* 对于非默认区域设置使用 wcstombs_l */
		fc_result = wcstombs_l(fc_to, fc_from, fc_tolen, fc_locale->info.lt);
#else							/* !HAVE_WCSTOMBS_L */
		/* 我们必须暂时将区域设置设置为当前...呃 */
		locale_t	fc_save_locale = uselocale(fc_locale->info.lt);

		fc_result = wcstombs(fc_to, fc_from, fc_tolen);

		uselocale(fc_save_locale);
#endif							/* HAVE_WCSTOMBS_L */
#else							/* !HAVE_LOCALE_T */
		/* 没有 HAVE_LOCALE_T 的情况下不能有 locale != 0 */
		elog(ERROR, "wcstombs_l is not available");
		fc_result = 0;				/* 保持编译器安静 */
#endif							/* HAVE_LOCALE_T */
	}

	return fc_result;
}

/*
 * char2wchar --- 将多字节字符转换为宽字符
 *
 * 这几乎具有 mbstowcs_l() 的 API，除了 *from 不必是
 * 以空结束；相反，输入字节的数量作为 fromlen 指定。
 * 此外，我们使用 ereport() 而不是返回 -1 来处理无效的
 * 输入编码。tolen 是存储在 *to 的最大 wchar_t 数量。
 * 输出将以零结尾，前提是有足够空间。
 */
size_t char2wchar(wchar_t *fc_to, size_t fc_tolen, const char *fc_from, size_t fc_fromlen,
		   pg_locale_t fc_locale)
{
	size_t		fc_result;

	Assert(!fc_locale || fc_locale->provider == COLLPROVIDER_LIBC);

	if (fc_tolen == 0)
		return 0;

#ifdef WIN32
	/* 请参阅上面的 WIN32 “Unicode” 注释 */
	if (GetDatabaseEncoding() == PG_UTF8)
	{
		/* Win32 API 对于零长度输入不起作用 */
		if (fromlen == 0)
			fc_result = 0;
		else
		{
			fc_result = MultiByteToWideChar(CP_UTF8, 0, from, fromlen, fc_to, fc_tolen - 1);
			/* 返回零表示失败 */
			if (fc_result == 0)
				fc_result = -1;
		}

		if (fc_result != -1)
		{
			Assert(fc_result < fc_tolen);
			/* 附加尾随的 null wchar（MultiByteToWideChar() 不会） */
			fc_to[fc_result] = 0;
		}
	}
	else
#endif							/* WIN32 */
	{
		/* mbstowcs 需要以 '\0' 结束 */
		char	   *fc_str = pnstrdup(fc_from, fc_fromlen);

		if (fc_locale == (pg_locale_t) 0)
		{
			/* 对于默认区域设置直接使用 mbstowcs */
			fc_result = mbstowcs(fc_to, fc_str, fc_tolen);
		}
		else
		{
#ifdef HAVE_LOCALE_T
#ifdef HAVE_MBSTOWCS_L
			/* 对于非默认区域设置使用 mbstowcs_l */
			fc_result = mbstowcs_l(fc_to, fc_str, fc_tolen, fc_locale->info.lt);
#else							/* !HAVE_MBSTOWCS_L */
			/* 我们必须暂时将区域设置设置为当前...呃 */
			locale_t	fc_save_locale = uselocale(fc_locale->info.lt);

			fc_result = mbstowcs(fc_to, fc_str, fc_tolen);

			uselocale(fc_save_locale);
#endif							/* HAVE_MBSTOWCS_L */
#else							/* !HAVE_LOCALE_T */
			/* 没有 HAVE_LOCALE_T 的情况下不能有 locale != 0 */
			elog(ERROR, "mbstowcs_l is not available");
			fc_result = 0;			/* 保持编译器安静 */
#endif							/* HAVE_LOCALE_T */
		}

		pfree(fc_str);
	}

	if (fc_result == -1)
	{
		/*
		 * 遇到无效的多字节字符。我们尝试通过让 pg_verifymbstr 检查字符串
		 * 来提供有用的错误消息。 但是字符串对我们来说可能是好的，而
		 * 对 mbstowcs 来说却不行——这表明 LC_CTYPE 区域与
		 * 数据库编码不同。如果 pg_verifymbstr 
		 * 找不到任何问题，则提供一个通用的错误消息。
		 */
		pg_verifymbstr(fc_from, fc_fromlen, false);	/* 可能不会返回 */
		/* 但如果返回了... */
		ereport(ERROR,
				(errcode(ERRCODE_CHARACTER_NOT_IN_REPERTOIRE),
				 errmsg("invalid multibyte character for locale"),
				 errhint("The server's LC_CTYPE locale is probably incompatible with the database encoding.")));
	}

	return fc_result;
}
